Skip to content

feat(serving): config validation, integration coverage, docs refresh (PR 5/5)#110

Merged
aksOps merged 1 commit intomainfrom
feat/prod-ready-5-config-it-docs
Apr 28, 2026
Merged

feat(serving): config validation, integration coverage, docs refresh (PR 5/5)#110
aksOps merged 1 commit intomainfrom
feat/prod-ready-5-config-it-docs

Conversation

@aksOps
Copy link
Copy Markdown
Contributor

@aksOps aksOps commented Apr 28, 2026

Summary

Final PR of the 5-PR production-readiness series. Closes the remaining audit findings around silent oversized-input clamping, missing end-to-end coverage of the serving filter chain, and stale tech-stack pins in CLAUDE.md.

  • MCP per-call input clamping in McpTools.queryNodes / queryEdges / getEgoGraph / searchGraph — caller-supplied limit clamped to mcp.limits.max_results, radius clamped to mcp.limits.max_depth. Defense-in-depth on top of PR 2's transaction-timeout cap so a limit=1_000_000 or radius=999 request gets the configured ceiling instead of an unbounded Cypher walk.
  • ConfigValidator hard ceilings + blank-string checksmax_payload_bytes / rate_per_minute / max_depth (1..100 ceiling — variable-length Cypher above 100 is pathological in practice; DoS sentinel) get explicit range validation; mcp.auth.token_env / mcp.auth.token blank-string fails validation when mode=bearer rather than silently coercing to null and fail-fasting at startup with a confusing message.
  • ServingChainIntegrationTest (new, 9 cases) — fills the gap where each filter (RequestIdFilter, SecurityHeadersFilter, RateLimitFilter, BearerAuthFilter) had unit-test coverage in isolation but no test exercised the full chain together. Asserts the cross-filter contract: 401 envelope with request_id echoed in X-Request-Id response header; 429 envelope with Retry-After + X-RateLimit-Remaining: 0; security headers on every response; inbound X-Request-Id propagation; control-char rejection (log-injection defense); rate-limit bucket isolation per token; /actuator/health auth bypass. Manually chains the four filters via lambda FilterChain instances — sub-second, no Spring context, no Neo4j.
  • CLAUDE.md tech-stack pin refresh — Spring Boot 4.0.5→4.0.6, Spring AI 2.0.0-M3→2.0.0-M4, Neo4j 2026.02.3→2026.04.0; added Bucket4j 8.18.0, logstash-logback-encoder 9.0, micrometer-registry-prometheus.

Test plan

  • mvn test -Dtest='ServingChainIntegrationTest' — 9/9 pass
  • Full suite: 3689 tests / 0 failures / 0 errors / 32 skipped
  • CI green (security.yml + scorecard.yml + ci-java.yml)
  • Auto-merge on green

Closes the production-readiness series

PR 1 (#106 — security baseline), PR 2 (#107 — resource limits), PR 3 (#108 — supply chain & bundle integrity), PR 4 (#109 — observability), PR 5 (this PR — config validation + IT + docs).

🤖 Generated with Claude Code

@aksOps aksOps enabled auto-merge (squash) April 28, 2026 09:44
…(PR 5/5)

Final PR of the production-readiness series. Closes the remaining audit
findings around silent oversized-input clamping, missing end-to-end
coverage of the serving filter chain, and stale tech-stack pins.

McpTools per-call clamping:
- queryNodes / queryEdges `limit` → clamped to mcp.limits.max_results
- getEgoGraph `radius`             → clamped to mcp.limits.max_depth
- searchGraph `limit`              → clamped to mcp.limits.max_results

Defense-in-depth on top of the transaction-timeout cap from PR 2 — a
caller asking for limit=1_000_000 or radius=999 now gets the configured
ceiling instead of an unbounded Cypher walk.

ConfigValidator hard ceilings + blank-string checks:
- mcp.limits.max_payload_bytes — must be > 0
- mcp.limits.rate_per_minute   — must be > 0
- mcp.limits.max_depth         — must be 1..100 (variable-length Cypher
  with depth >100 is pathological — DoS sentinel)
- mcp.auth.token_env / .token  — blank fails validation when mode=bearer

ServingChainIntegrationTest (new, 9 cases):
- 401 envelope shape with X-Request-Id correlation
- 200 + valid token pass-through
- 401 + wrong token
- security headers present on success responses
- inbound X-Request-Id propagation when valid
- inbound control-char rejection (log-injection defense)
- 429 envelope with Retry-After + X-RateLimit-Remaining: 0
- /actuator/health bypasses auth (kubelet probes)
- rate-limit bucket isolation per token

Manually chains the four filters via lambda FilterChain instances —
runs sub-second, no @SpringBootTest, no Neo4j.

CLAUDE.md tech-stack pins: Spring Boot 4.0.5→4.0.6, Spring AI
2.0.0-M3→2.0.0-M4, Neo4j 2026.02.3→2026.04.0; added Bucket4j 8.18.0,
logstash-logback-encoder 9.0, micrometer-registry-prometheus.

Tests: 3689 / 0 / 0 / 32 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@aksOps aksOps force-pushed the feat/prod-ready-5-config-it-docs branch from 80c822b to 54c3eba Compare April 28, 2026 09:45
@aksOps aksOps merged commit 9f0d3d2 into main Apr 28, 2026
13 checks passed
@aksOps aksOps deleted the feat/prod-ready-5-config-it-docs branch April 28, 2026 09:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant